use entities::task;
use sea_orm::{ActiveValue::NotSet, Set};
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Task {
    pub id: Option<i32>,
    #[serde(skip)]
    pub uri: String,
    pub status: Status,
    pub category: Category,
    pub gid: String,
    pub name: String,
    pub created_at: i64,
    #[serde(flatten)]
    pub detail_data: Detail,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Detail {
    pub dir: String,
    pub files: Vec<File>,
    pub connections: u32,
    pub download_speed: u64,
    pub total_length: u64,
    pub completed_length: u64,
    pub upload_speed: u64,
    pub upload_length: u64,
    pub followed_by: Option<Vec<String>>,
    pub following: Option<String>,
    pub mode: Option<String>,
    pub error_code: Option<String>,
    pub seeder: Option<bool>,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct File {
    pub index: u32,
    pub path: String,
    pub completed_length: u64,
    pub length: u64,
    pub selected: bool,
}

impl Task {
    pub fn into_active_model(self) -> task::ActiveModel {
        let detail = serde_json::to_string(&self.detail_data).unwrap();

        task::ActiveModel {
            id: self.id.map_or(NotSet, Set),
            uri: Set(self.uri),
            status: Set(self.status.into()),
            category: Set(self.category.into()),
            gid: Set(self.gid),
            name: Set(self.name),
            detail: Set(detail),
            created_at: Set(self.created_at),
        }
    }

    pub fn into_update_model(self) -> task::ActiveModel {
        let detail = serde_json::to_string(&self.detail_data).unwrap();

        task::ActiveModel {
            id: Set(self.id.unwrap()),
            status: Set(self.status.into()),
            detail: Set(detail),
            ..Default::default()
        }
    }

    pub fn into_update_status(self) -> task::ActiveModel {
        task::ActiveModel {
            id: Set(self.id.unwrap()),
            status: Set(self.status.into()),
            ..Default::default()
        }
    }

    pub fn into_update_gid(self) -> task::ActiveModel {
        task::ActiveModel {
            id: Set(self.id.unwrap()),
            gid: Set(self.gid),
            status: Set(self.status.into()),
            ..Default::default()
        }
    }

    pub fn from_model(model: task::Model) -> Self {
        let detail: Detail = serde_json::from_str(&model.detail).unwrap();

        Self {
            id: Some(model.id),
            uri: model.uri,
            status: model.status.into(),
            category: model.category.into(),
            gid: model.gid,
            name: model.name,
            created_at: model.created_at,
            detail_data: detail,
        }
    }
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Category {
    #[default]
    File,
    Magnet,
    BitTorrent,
}

impl From<Category> for String {
    fn from(val: Category) -> Self {
        match val {
            Category::File => "file",
            Category::Magnet => "magnet",
            Category::BitTorrent => "bt",
        }
        .into()
    }
}

impl From<String> for Category {
    fn from(value: String) -> Self {
        match value.as_str() {
            "file" => Category::File,
            "magnet" => Category::Magnet,
            "bt" => Category::BitTorrent,
            _ => Category::default(),
        }
    }
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Status {
    #[default]
    Waiting,
    Active,
    Pause,
    Seeding,
    Complete,
    Error,
}

impl From<Status> for String {
    fn from(value: Status) -> Self {
        match value {
            Status::Waiting => "waiting",
            Status::Active => "active",
            Status::Pause => "pause",
            Status::Seeding => "seeding",
            Status::Complete => "complete",
            Status::Error => "error",
        }
        .into()
    }
}

impl From<String> for Status {
    fn from(value: String) -> Self {
        match value.as_str() {
            "waiting" => Status::Waiting,
            "active" => Status::Active,
            "pause" => Status::Pause,
            "seeding" => Status::Seeding,
            "complete" => Status::Complete,
            "error" => Status::Error,
            _ => Status::default(),
        }
    }
}
